<?php

namespace Illuminate\Foundation\Bootstrap;

use Exception;
use Illuminate\Config\Repository;
use Illuminate\Contracts\Config\Repository as RepositoryContract;
use Illuminate\Contracts\Foundation\Application;
use SplFileInfo;
use Symfony\Component\Finder\Finder;

class LoadConfiguration
{
    /**
     * Bootstrap the given application.
     *
     * @param  \Illuminate\Contracts\Foundation\Application $app
     * @return void
     */
    public function bootstrap(Application $app)
    {
        $items = [];

        // 首先检查有无配置项的缓存文件，从缓存文件加载可以减少文件遍历的性能损耗
        if (file_exists($cached = $app->getCachedConfigPath())) {
            $items = require $cached;

            $loadedFromCache = true;
        }

        // 真正的 config 服务实例是在这里被绑定
        $app->instance('config', $config = new Repository($items));

        // 没有使用缓存的话，我们将浏览所有的配置文件并加载，把每个配置文件都加载到配置存储库中
        if (!isset($loadedFromCache)) {
            $this->loadConfigurationFiles($app, $config);
        }

        // 最后我们将根据加载的配置值设置应用程序的执行环境，我们将传递一个回调函数，
        // 它将用于系统在不存在 "--env" 开关的 web 上下文中获取执行环境
        $app->detectEnvironment(function () use ($config) {
            return $config->get('app.env', 'production');
        });

        // 设置默认时区
        date_default_timezone_set($config->get('app.timezone', 'UTC'));

        // 设置内部字符编码为 UTF-8
        mb_internal_encoding('UTF-8');
    }

    /**
     * 从所有的配置文件中加载配置项
     *
     * @param  \Illuminate\Contracts\Foundation\Application $app 应用对象
     * @param  \Illuminate\Contracts\Config\Repository $repository 配置存储库
     * @return void
     *
     * @throws \Exception
     */
    protected function loadConfigurationFiles(Application $app, RepositoryContract $repository)
    {
        $files = $this->getConfigurationFiles($app);

        if (!isset($files['app'])) {
            throw new Exception('Unable to load the "app" configuration file.');
        }

        // 配置文件的文件名当做存储键，并 require 配置文件，把信息存储到配置服务中
        foreach ($files as $key => $path) {
            $repository->set($key, require $path);
        }
    }

    /**
     * 获取应用程序的所有配置文件路径
     *
     * @param  \Illuminate\Contracts\Foundation\Application $app
     * @return array
     */
    protected function getConfigurationFiles(Application $app)
    {
        $files = [];

        $configPath = realpath($app->configPath());

        // 提取配置目录下的所有 php 文件，并把文件名作为键名记录下来
        // 如果存在子目录配置项，则记录键名格式为 [目录名.文件名]
        foreach (Finder::create()->files()->name('*.php')->in($configPath) as $file) {
            $directory = $this->getNestedDirectory($file, $configPath);

            $files[$directory . basename($file->getRealPath(), '.php')] = $file->getRealPath();
        }

        ksort($files, SORT_NATURAL);

        return $files;
    }

    /**
     * 获取配置文件的嵌套路径
     *
     * @param  \SplFileInfo $file
     * @param  string $configPath
     * @return string
     */
    protected function getNestedDirectory(SplFileInfo $file, $configPath)
    {
        $directory = $file->getPath();

        // 判断配置文件是否是存在于 config 目录的子目录中，如果是，就解析出子目录的目录名返回
        if ($nested = trim(str_replace($configPath, '', $directory), DIRECTORY_SEPARATOR)) {
            $nested = str_replace(DIRECTORY_SEPARATOR, '.', $nested) . '.';
        }

        return $nested;
    }
}
